﻿using DevExpress.Internal;
using Newtonsoft.Json;
using Stee.CAP8.MessageBus.Sagas.Core.HanlderInvokers;
using Stee.CAP8.MessageBus.Sagas.Core.HanlderInvokers.New;
using Stee.CAP8.MessageBus.Sagas.Core.Serializaion;
using Stee.CAP8.MessageBus.Sagas.Core.Steps;
using Stee.CAP8.MessageBus.Sagas.Core.SubscribeStores;
using Stee.CAP8.MessageBus.Sagas.Core.Transactions;
using Stee.CAP8.MessageBus.Sagas.Messages.Messages;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Stee.CAP8.MessageBus.Sagas.Core.Pipeline.Reiceve
{
    public class ActivateHandlersStep : IIncomingStep
    {
        readonly ISubscribeStore _subscribeStore;
        readonly ConcurrentDictionary<Type, MethodInfo> _dispatchMethods = new ConcurrentDictionary<Type, MethodInfo>();

        private SimpleAssemblyQualifiedMessageTypeNameConvention messageTypeNameConvention;

        public ActivateHandlersStep(ISubscribeStore subscribeStore)
        {
            messageTypeNameConvention = new SimpleAssemblyQualifiedMessageTypeNameConvention();
            _subscribeStore = subscribeStore ?? throw new ArgumentNullException(nameof(subscribeStore));
        }

        public async Task Process(IncomingStepContext context, Func<Task> next)
        {
            var transactionContext = context.Load<ITransactionContext>(); 
            var message = context.Load<TransportMessage>();

            var typeObject = messageTypeNameConvention.GetType(message.Action);
            var body = JsonConvert.DeserializeObject(message.Body, typeObject);
            var messageType = body.GetType();
            var methodToInvoke = _dispatchMethods
                .GetOrAdd(messageType, type => GetDispatchMethod(messageType));
            int handlersInvoked = 0;
            var handlerInvokers = await ((Task<HandlerInvokers>)methodToInvoke.Invoke(this, new[] { body, transactionContext, message }));
            for (int index = 0; index < handlerInvokers.Count(); index++)
            {
                var invoker = handlerInvokers[index];

                await invoker.Invoke();
                handlersInvoked++;
            }
            await next();


        }
        async Task<HandlerInvokers> GetHandlerInvokers<TMessage>(TMessage message, ITransactionContext transactionContext, TransportMessage logicalMessage)
        {
            var handlers =  _subscribeStore.GetMessageHandlers<TMessage>();

            var listOfHandlerInvokers = handlers
                .Select(handler => CreateHandlerInvoker(handler, message, transactionContext, logicalMessage))
                .ToList();

            return new HandlerInvokers(logicalMessage, listOfHandlerInvokers);
        }

        /// <summary>
        /// Creates instance of HandlerInvoker
        /// </summary>
        protected virtual HandlerInvoker CreateHandlerInvoker<TMessage>(IHandleMessages<TMessage> handler, TMessage message, ITransactionContext transactionContext, TransportMessage logicalMessage)
        {
            return new HandlerInvoker<TMessage>(() => handler.Handle(message), handler);
        }

        MethodInfo GetDispatchMethod(Type messageType)
        {
            const string methodName = nameof(GetHandlerInvokers);

            var genericDispatchMethod = GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance)
                ?? throw new ArgumentException($"Could not find the {methodName} method?!");

            return genericDispatchMethod.MakeGenericMethod(messageType);
        }
    }
}
